package com.xmxsuperstar.app.ttac.collections;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.RandomAccessFile;
import java.io.Serializable;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Iterator;

public class FArrayList<T extends Serializable> implements FList<T> {
	private RandomAccessFile dataFile;
	private final File file;

	private final static int MAX_SIZE = 50000;

	private final static int SIZE_AREA_BYTES = Integer.SIZE / 8;

	private final static int INDEX_ITEM_BYTES = (Long.SIZE + Integer.SIZE) / 8;
	private final static int INDEX_AREA_BYTES = MAX_SIZE * INDEX_ITEM_BYTES;

	public FArrayList(File file) {
		this.file = file;
		dataFile = null;
		try {
			if (!file.exists()) {
				File parent = file.getAbsoluteFile().getParentFile();
				parent.mkdirs();
				file.createNewFile();
				dataFile = new RandomAccessFile(file, "rw");
				FileChannel fileChannel = dataFile.getChannel();
				MappedByteBuffer mappedByteBuffer = fileChannel.map(
						FileChannel.MapMode.READ_WRITE, 0L, SIZE_AREA_BYTES
								+ INDEX_AREA_BYTES);
				mappedByteBuffer.putInt(0); // size
				mappedByteBuffer.force();
			} else {
				File newFile = new File(file.getAbsoluteFile().getParentFile(),
						file.getName() + ".new");
				if (newFile.exists()) {
					boolean delRes = file.delete();
					if (!delRes) {
						throw new RuntimeException(
								"TTListInFile delete failed!");
					}
					newFile.renameTo(file);
				}
				dataFile = new RandomAccessFile(file, "rw");
			}
		} catch (IOException e) {
		}
	}

	@Override
	public Iterator<T> iterator() {
		return new Itr();
	}

	private class Itr implements Iterator<T> {

		int curse = 0;

		@Override
		public boolean hasNext() {
			return curse < FArrayList.this.size();
		}

		@Override
		public T next() {
			return FArrayList.this.get(curse++);
		}

		@Override
		public void remove() {
			// do nothing
		}

	}

	@Override
	public boolean add(T t) {

		if (this.size() == MAX_SIZE) {
			return false;
		}

		try {

			int size = this.size();

			byte[] bytes = getObjectBytes(t);

			long currentDataFileLength = dataFile.length();

			// 移动index
			for (int i = size - 1; i >= 0; i--) {
				IndexItem indexItem = this.readIndexItem(i);
				this.writeIndexItem(i + 1, indexItem);
			}

			// 写入新的index
			IndexItem indexItem = new IndexItem();
			indexItem.dataOffset = currentDataFileLength;
			indexItem.dataLength = bytes.length;
			this.writeIndexItem(0, indexItem);

			// 写入新的size
			this.writeSize(size + 1);

			// // 写入对象数据
			this.writeObjectData(bytes);

			return true;
		} catch (IOException e) {
		}
		return false;
	}

	@Override
	public boolean append(T t) {
		if (this.size() == MAX_SIZE) {
			return false;
		}

		try {

			int size = this.size();

			byte[] bytes = getObjectBytes(t);

			long currentDataFileLength = dataFile.length();

			// 写入新的index

			IndexItem indexItem = new IndexItem();
			indexItem.dataOffset = currentDataFileLength;
			indexItem.dataLength = bytes.length;
			this.writeIndexItem(size, indexItem);

			// 写入新的size
			this.writeSize(size + 1);
			// 写入对象数据
			this.writeObjectData(bytes);

			return true;
		} catch (IOException e) {
		}
		return false;
	}

	@SuppressWarnings("unchecked")
	@Override
	public T get(int index) {

		if (index < 0 || index >= this.size()) {
			throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
					+ this.size());
		}

		try {
			IndexItem indexItem = this.readIndexItem(index);

			byte[] data = this.readObjectData(indexItem.dataOffset,
					indexItem.dataLength);

			ObjectInputStream ois = new ObjectInputStream(
					new ByteArrayInputStream(data));
			Object o = ois.readObject();
			return (T) o;
		} catch (IOException ioe) {
		} catch (ClassNotFoundException e) {
		}
		return null;

	}

	@Override
	public int size() {
		try {
			FileChannel fileChannel = dataFile.getChannel();
			MappedByteBuffer mappedByteBuffer;
			mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY,
					0L, SIZE_AREA_BYTES);
			return mappedByteBuffer.getInt(0);
		} catch (IOException e) {
		}
		return -1;
	}

	@Override
	public boolean isEmpty() {
		return this.size() <= 0;
	}

	@Override
	public boolean clear() {
		if (this.size() > 0) {
			try {
				dataFile.close();
				File newFile = new File(file.getAbsoluteFile().getParentFile(),
						file.getName() + ".new");
				dataFile = new RandomAccessFile(newFile, "rw");
				FileChannel fileChannel = dataFile.getChannel();
				MappedByteBuffer mappedByteBuffer = fileChannel.map(
						FileChannel.MapMode.READ_WRITE, 0L, SIZE_AREA_BYTES
								+ INDEX_AREA_BYTES);
				mappedByteBuffer.putInt(0); // size
				mappedByteBuffer.force();
			} catch (IOException e) {
				return false;
			}

		}

		return true;

	}

	private byte[] getObjectBytes(Object o) throws IOException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(baos);
		oos.writeObject(o);
		byte[] bytes = baos.toByteArray();
		return bytes;
	}

	private boolean writeSize(int size) {
		try {
			FileChannel fileChannel = dataFile.getChannel();
			MappedByteBuffer mappedByteBuffer1 = fileChannel.map(
					FileChannel.MapMode.READ_WRITE, 0, SIZE_AREA_BYTES);

			mappedByteBuffer1.putInt(size);
			mappedByteBuffer1.force();
			return true;
		} catch (IOException e) {
		}

		return false;
	}

	private IndexItem readIndexItem(int index) {
		try {
			FileChannel fileChannel = dataFile.getChannel();
			MappedByteBuffer mappedByteBuffer1 = fileChannel.map(
					FileChannel.MapMode.READ_ONLY, SIZE_AREA_BYTES,
					INDEX_AREA_BYTES);

			IndexItem indexItem = new IndexItem();
			indexItem.dataOffset = mappedByteBuffer1.getLong(index
					* INDEX_ITEM_BYTES);
			indexItem.dataLength = mappedByteBuffer1.getInt(index
					* INDEX_ITEM_BYTES + Long.SIZE / 8);

			return indexItem;

		} catch (IOException e) {
		}
		return null;
	}

	private boolean writeIndexItem(int index, IndexItem indexItem) {
		try {
			FileChannel fileChannel = dataFile.getChannel();
			MappedByteBuffer mappedByteBuffer1 = fileChannel.map(
					FileChannel.MapMode.READ_WRITE, SIZE_AREA_BYTES,
					INDEX_AREA_BYTES);

			mappedByteBuffer1.putLong(index * INDEX_ITEM_BYTES,
					indexItem.dataOffset);
			mappedByteBuffer1.putInt(index * INDEX_ITEM_BYTES + Long.SIZE / 8,
					indexItem.dataLength);

			mappedByteBuffer1.force();

			return true;

		} catch (IOException e) {
		}
		return false;
	}

	private class IndexItem {
		long dataOffset;
		int dataLength;
	}

	private boolean writeObjectData(byte[] data) {
		try {
			long currentDataFileLength = dataFile.length();
			FileChannel fileChannel = dataFile.getChannel();
			MappedByteBuffer mappedByteBuffer2 = fileChannel.map(
					FileChannel.MapMode.READ_WRITE, currentDataFileLength,
					data.length);

			// append object data to the end of the file;
			mappedByteBuffer2.put(data);
			mappedByteBuffer2.force();
			return true;
		} catch (IOException e) {
		}

		return false;
	}

	private byte[] readObjectData(long offset, int length) {
		try {
			byte[] bytes = new byte[length];

			FileChannel fileChannel = dataFile.getChannel();
			MappedByteBuffer mappedByteBuffer2 = fileChannel.map(
					FileChannel.MapMode.READ_ONLY, offset, length);

			mappedByteBuffer2.get(bytes);

			return bytes;
		} catch (IOException e) {
		}

		return null;
	}
}
